/*
 * This file is part of muCommander, http://www.mucommander.com
 *
 * muCommander is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * muCommander is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mucommander.ui.action;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.ImageIcon;
import javax.swing.KeyStroke;

import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mucommander.command.Command;
import com.mucommander.command.CommandManager;
import com.mucommander.command.CommandType;
import com.mucommander.desktop.ActionType;
import com.mucommander.ui.action.impl.*;
import com.mucommander.ui.main.MainFrame;

/**
 * ActionManager provides methods to retrieve {@link MuAction} instances and invoke them. It keeps track of all the
 * action instances it has created and allows them to be reused within a {@link MainFrame}.
 *
 * <p>MuAction subclasses should not be instantiated directly, <code>getActionInstance</code>
 * methods should be used instead. Using ActionManager to retrieve a MuAction ensures that only one instance
 * exists for a given {@link MainFrame}. This is particularly important because actions are stateful and can be used
 * in several components of a MainFrame at the same time; if an action's state changes, the change must be reflected
 * everywhere the action is used. It is also important for performance reasons: sharing one action throughout a
 * {@link com.mucommander.ui.main.MainFrame} saves some memory and also CPU cycles as some actions listen to particular events to change
 * their state accordingly.</p>
 *
 * @see MuAction
 * @see ActionParameters
 * @see ActionKeymap
 * @author Maxence Bernard, Arik Hadas
 */
public class ActionManager {
    private static final Logger LOGGER = LoggerFactory.getLogger(ActionManager.class);

    /** MuAction id -> factory map */
    private static Map<ActionId, ActionFactory> actionFactories = new ConcurrentHashMap<>();

    /** MainFrame -> MuAction map */
    private static WeakHashMap<MainFrame, Map<ActionParameters, ActionAndIdPair>> mainFrameActionsMap = new WeakHashMap<>();

    /** Pattern to resolve the action ID from action class path */
    private final static Pattern pattern = Pattern.compile(".*\\.(.*)?Action");

    public static void registerActions() {
        registerAction(new AddBookmarkAction.Descriptor(),                  AddBookmarkAction::new);
        registerAction(new NewTabAction.Descriptor(),                       NewTabAction::new);
        registerAction(new BatchRenameAction.Descriptor(),                  BatchRenameAction::new);
        registerAction(new BringAllToFrontAction.Descriptor(),              BringAllToFrontAction::new);
        registerAction(new CalculateChecksumAction.Descriptor(),            CalculateChecksumAction::new);
        registerAction(new ChangeDateAction.Descriptor(),                   ChangeDateAction::new);
        registerAction(new ChangeLocationAction.Descriptor(),               ChangeLocationAction::new);
        registerAction(new ChangePermissionsAction.Descriptor(),            ChangePermissionsAction::new);
        registerAction(new CheckForUpdatesAction.Descriptor(),              CheckForUpdatesAction::new);
        registerAction(new CloneTabToOtherPanelAction.Descriptor(),         CloneTabToOtherPanelAction::new);
        registerAction(new CloseDuplicateTabsAction.Descriptor(),           CloseDuplicateTabsAction::new);
        registerAction(new CloseOtherTabsAction.Descriptor(),               CloseOtherTabsAction::new);
        registerAction(new CloseWindowAction.Descriptor(),                  CloseWindowAction::new);
        registerAction(new CloseTabAction.Descriptor(),                     CloseTabAction::new);
        registerAction(new CompareFoldersAction.Descriptor(),               CompareFoldersAction::new);
        registerAction(new ConnectToServerAction.Descriptor(),              ConnectToServerAction::new);
        registerAction(new CopyAction.Descriptor(),                         CopyAction::new);
        registerAction(new CopyFileBaseNamesAction.Descriptor(),            CopyFileBaseNamesAction::new);
        registerAction(new CopyFileNamesAction.Descriptor(),                CopyFileNamesAction::new);
        registerAction(new CopyFilePathsAction.Descriptor(),                CopyFilePathsAction::new);
        registerAction(new CopyFilesToClipboardAction.Descriptor(),         CopyFilesToClipboardAction::new);
        registerAction(new FindAction.Descriptor(),                         FindAction::new);
        registerAction(new FocusPreviousAction.Descriptor(),                FocusPreviousAction::new);
        registerAction(new FocusNextAction.Descriptor(),                    FocusNextAction::new);
        registerAction(new DeleteAction.Descriptor(),                       DeleteAction::new);
        registerAction(new DonateAction.Descriptor(),                       DonateAction::new);
        registerAction(new DuplicateTabAction.Descriptor(),                 DuplicateTabAction::new);
        registerAction(new EditAction.Descriptor(),                         EditAction::new);
        registerAction(new EditBookmarksAction.Descriptor(),                EditBookmarksAction::new);
        registerAction(new EditCredentialsAction.Descriptor(),              EditCredentialsAction::new);
        registerAction(new EmailAction.Descriptor(),                        EmailAction::new);
        registerAction(new EmptyTrashAction.Descriptor(),                   EmptyTrashAction::new);
        registerAction(new ExploreBookmarksAction.Descriptor(),             ExploreBookmarksAction::new);
        registerAction(new GoBackAction.Descriptor(),                       GoBackAction::new);
        registerAction(new GoForwardAction.Descriptor(),                    GoForwardAction::new);
        registerAction(new GoToDocumentationAction.Descriptor(),            GoToDocumentationAction::new);
        registerAction(new GoToForumsAction.Descriptor(),                   GoToForumsAction::new);
        registerAction(new GoToHomeAction.Descriptor(),                     GoToHomeAction::new);
        registerAction(new GoToParentAction.Descriptor(),                   GoToParentAction::new);
        registerAction(new GoToParentInBothPanelsAction.Descriptor(),       GoToParentInBothPanelsAction::new);
        registerAction(new GoToParentInOtherPanelAction.Descriptor(),       GoToParentInOtherPanelAction::new);
        registerAction(new GoToRootAction.Descriptor(),                     GoToRootAction::new);
        registerAction(new GoToWebsiteAction.Descriptor(),                  GoToWebsiteAction::new);
        registerAction(new InternalEditAction.Descriptor(),                 InternalEditAction::new);
        registerAction(new InternalViewAction.Descriptor(),                 InternalViewAction::new);
        registerAction(new InvertSelectionAction.Descriptor(),              InvertSelectionAction::new);
        registerAction(new LocalCopyAction.Descriptor(),                    LocalCopyAction::new);
        registerAction(new MarkAllAction.Descriptor(),                      MarkAllAction::new);
        registerAction(new MarkExtensionAction.Descriptor(),                MarkExtensionAction::new);
        registerAction(new MarkGroupAction.Descriptor(),                    MarkGroupAction::new);
        registerAction(new MarkNextBlockAction.Descriptor(),                MarkNextBlockAction::new);
        registerAction(new MarkNextPageAction.Descriptor(),                 MarkNextPageAction::new);
        registerAction(new MarkNextRowAction.Descriptor(),                  MarkNextRowAction::new);
        registerAction(new MarkPreviousBlockAction.Descriptor(),            MarkPreviousBlockAction::new);
        registerAction(new MarkPreviousPageAction.Descriptor(),             MarkPreviousPageAction::new);
        registerAction(new MarkPreviousRowAction.Descriptor(),              MarkPreviousRowAction::new);
        registerAction(new MarkSelectedFileAction.Descriptor(),             MarkSelectedFileAction::new);
        registerAction(new MarkToFirstRowAction.Descriptor(),               MarkToFirstRowAction::new);
        registerAction(new MarkToLastRowAction.Descriptor(),                MarkToLastRowAction::new);
        registerAction(new MaximizeWindowAction.Descriptor(),               MaximizeWindowAction::new);
        registerAction(new CombineFilesAction.Descriptor(),                 CombineFilesAction::new);
        registerAction(new MinimizeWindowAction.Descriptor(),               MinimizeWindowAction::new);
        registerAction(new MkdirAction.Descriptor(),                        MkdirAction::new);
        registerAction(new MkfileAction.Descriptor(),                       MkfileAction::new);
        registerAction(new MoveAction.Descriptor(),                         MoveAction::new);
        registerAction(new MoveTabToOtherPanelAction.Descriptor(),          MoveTabToOtherPanelAction::new);
        registerAction(new NewWindowAction.Descriptor(),                    NewWindowAction::new);
        registerAction(new NextTabAction.Descriptor(),                      NextTabAction::new);
        registerAction(new OpenAction.Descriptor(),                         OpenAction::new);
        registerAction(new OpenAsAction.Descriptor(),                       OpenAsAction::new);
        registerAction(new OpenCommandPromptAction.Descriptor(),            OpenCommandPromptAction::new);
        registerAction(new OpenInBothPanelsAction.Descriptor(),             OpenInBothPanelsAction::new);
        registerAction(new OpenInNewTabAction.Descriptor(),                 OpenInNewTabAction::new);
        registerAction(new OpenInOtherPanelAction.Descriptor(),             OpenInOtherPanelAction::new);
        registerAction(new OpenNativelyAction.Descriptor(),                 OpenNativelyAction::new);
        registerAction(new OpenTrashAction.Descriptor(),                    OpenTrashAction::new);
        registerAction(new OpenURLInBrowserAction.Descriptor(),             OpenURLInBrowserAction::new);
        registerAction(new PackAction.Descriptor(),                         PackAction::new);
        registerAction(new PasteClipboardFilesAction.Descriptor(),          PasteClipboardFilesAction::new);
        registerAction(new PermanentDeleteAction.Descriptor(),              PermanentDeleteAction::new);
        registerAction(new PopupLeftDriveButtonAction.Descriptor(),         PopupLeftDriveButtonAction::new);
        registerAction(new PopupRightDriveButtonAction.Descriptor(),        PopupRightDriveButtonAction::new);
        registerAction(new PreviousTabAction.Descriptor(),                  PreviousTabAction::new);
        registerAction(new QuickFindAction.Descriptor(),                    QuickFindAction::new);
        registerAction(new QuitAction.Descriptor(),                         QuitAction::new);
        registerAction(new RecallNextWindowAction.Descriptor(),             RecallNextWindowAction::new);
        registerAction(new RecallPreviousWindowAction.Descriptor(),         RecallPreviousWindowAction::new);
        registerAction(new RecallWindow10Action.Descriptor(),               RecallWindow10Action::new);
        registerAction(new RecallWindow1Action.Descriptor(),                RecallWindow1Action::new);
        registerAction(new RecallWindow2Action.Descriptor(),                RecallWindow2Action::new);
        registerAction(new RecallWindow3Action.Descriptor(),                RecallWindow3Action::new);
        registerAction(new RecallWindow4Action.Descriptor(),                RecallWindow4Action::new);
        registerAction(new RecallWindow5Action.Descriptor(),                RecallWindow5Action::new);
        registerAction(new RecallWindow6Action.Descriptor(),                RecallWindow6Action::new);
        registerAction(new RecallWindow7Action.Descriptor(),                RecallWindow7Action::new);
        registerAction(new RecallWindow8Action.Descriptor(),                RecallWindow8Action::new);
        registerAction(new RecallWindow9Action.Descriptor(),                RecallWindow9Action::new);
        registerAction(new RecallWindowAction.Descriptor(),                 RecallWindowAction::new);
        registerAction(new RefreshAction.Descriptor(),                      RefreshAction::new);
        registerAction(new RenameAction.Descriptor(),                       RenameAction::new);
        registerAction(new ReportBugAction.Descriptor(),                    ReportBugAction::new);
        registerAction(new RevealInDesktopAction.Descriptor(),              RevealInDesktopAction::new);
        registerAction(new ReverseSortOrderAction.Descriptor(),             ReverseSortOrderAction::new);
        registerAction(new ShowInEnclosingFolderAction.Descriptor(),        ShowInEnclosingFolderAction::new);
        registerAction(new SelectPreviousBlockAction.Descriptor(),          SelectPreviousBlockAction::new);
        registerAction(new SelectPreviousPageAction.Descriptor(),           SelectPreviousPageAction::new);
        registerAction(new SelectPreviousRowAction.Descriptor(),            SelectPreviousRowAction::new);
        registerAction(new SelectNextBlockAction.Descriptor(),              SelectNextBlockAction::new);
        registerAction(new SelectNextPageAction.Descriptor(),               SelectNextPageAction::new);
        registerAction(new SelectNextRowAction.Descriptor(),                SelectNextRowAction::new);
        registerAction(new SelectFirstRowAction.Descriptor(),               SelectFirstRowAction::new);
        registerAction(new SelectLastRowAction.Descriptor(),                SelectLastRowAction::new);
        registerAction(new SetSameFolderAction.Descriptor(),                SetSameFolderAction::new);
        registerAction(new SetTabTitleAction.Descriptor(),                  SetTabTitleAction::new);
        registerAction(new ShowAboutAction.Descriptor(),                    ShowAboutAction::new);
        registerAction(new ShowBookmarksQLAction.Descriptor(),              ShowBookmarksQLAction::new);
        registerAction(new CustomizeCommandBarAction.Descriptor(),          CustomizeCommandBarAction::new);
        registerAction(new ShowDebugConsoleAction.Descriptor(),             ShowDebugConsoleAction::new);
        registerAction(new ShowFilePropertiesAction.Descriptor(),           ShowFilePropertiesAction::new);
        registerAction(new ShowFilePopupMenuAction.Descriptor(),            ShowFilePopupMenuAction::new);
        registerAction(new ShowKeyboardShortcutsAction.Descriptor(),        ShowKeyboardShortcutsAction::new);
        registerAction(new ShowParentFoldersQLAction.Descriptor(),          ShowParentFoldersQLAction::new);
        registerAction(new ShowPreferencesAction.Descriptor(),              ShowPreferencesAction::new);
        registerAction(new ShowRecentExecutedFilesQLAction.Descriptor(),    ShowRecentExecutedFilesQLAction::new);
        registerAction(new ShowRecentLocationsQLAction.Descriptor(),        ShowRecentLocationsQLAction::new);
        registerAction(new ShowRootFoldersQLAction.Descriptor(),            ShowRootFoldersQLAction::new);
        registerAction(new ShowServerConnectionsAction.Descriptor(),        ShowServerConnectionsAction::new);
        registerAction(new ShowTabsQLAction.Descriptor(),                   ShowTabsQLAction::new);
        registerAction(new SortByDateAction.Descriptor(),                   SortByDateAction::new);
        registerAction(new SortByExtensionAction.Descriptor(),              SortByExtensionAction::new);
        registerAction(new SortByGroupAction.Descriptor(),                  SortByGroupAction::new);
        registerAction(new SortByNameAction.Descriptor(),                   SortByNameAction::new);
        registerAction(new SortByOwnerAction.Descriptor(),                  SortByOwnerAction::new);
        registerAction(new SortByPermissionsAction.Descriptor(),            SortByPermissionsAction::new);
        registerAction(new SortBySizeAction.Descriptor(),                   SortBySizeAction::new);
        registerAction(new SplitEquallyAction.Descriptor(),                 SplitEquallyAction::new);
        registerAction(new SplitFileAction.Descriptor(),                    SplitFileAction::new);
        registerAction(new SplitHorizontallyAction.Descriptor(),            SplitHorizontallyAction::new);
        registerAction(new SplitVerticallyAction.Descriptor(),              SplitVerticallyAction::new);
        registerAction(new ToggleUseSinglePanelAction.Descriptor(),         ToggleUseSinglePanelAction::new);
        registerAction(new StopAction.Descriptor(),                         StopAction::new);
        registerAction(new SwapFoldersAction.Descriptor(),                  SwapFoldersAction::new);
        registerAction(new SwitchActiveTableAction.Descriptor(),            SwitchActiveTableAction::new);
        registerAction(new ToggleAutoSizeAction.Descriptor(),               ToggleAutoSizeAction::new);
        registerAction(new ToggleCommandBarAction.Descriptor(),             ToggleCommandBarAction::new);
        registerAction(new ToggleDateColumnAction.Descriptor(),             ToggleDateColumnAction::new);
        registerAction(new ToggleExtensionColumnAction.Descriptor(),        ToggleExtensionColumnAction::new);
        registerAction(new ToggleGroupColumnAction.Descriptor(),            ToggleGroupColumnAction::new);
        registerAction(new ToggleHiddenFilesAction.Descriptor(),            ToggleHiddenFilesAction::new);
        registerAction(new ToggleLockTabAction.Descriptor(),                ToggleLockTabAction::new);
        registerAction(new ToggleOwnerColumnAction.Descriptor(),            ToggleOwnerColumnAction::new);
        registerAction(new ToggleShowParentFolderAction.Descriptor(),       ToggleShowParentFolderAction::new);
        registerAction(new TogglePermissionsColumnAction.Descriptor(),      TogglePermissionsColumnAction::new);
        registerAction(new ToggleShowFoldersFirstAction.Descriptor(),       ToggleShowFoldersFirstAction::new);
        registerAction(new ToggleSizeColumnAction.Descriptor(),             ToggleSizeColumnAction::new);
        registerAction(new ToggleStatusBarAction.Descriptor(),              ToggleStatusBarAction::new);
        registerAction(new ToggleTerminalAction.Descriptor(),               ToggleTerminalAction::new);
        registerAction(new ToggleToolBarAction.Descriptor(),                ToggleToolBarAction::new);
        registerAction(new ToggleTreeAction.Descriptor(),                   ToggleTreeAction::new);
        registerAction(new UnmarkAllAction.Descriptor(),                    UnmarkAllAction::new);
        registerAction(new UnmarkGroupAction.Descriptor(),                  UnmarkGroupAction::new);
        registerAction(new UnpackAction.Descriptor(),                       UnpackAction::new);
        registerAction(new ViewAction.Descriptor(),                         ViewAction::new);

        // register "open with" commands as actions, to allow for keyboard shortcuts for them
        CommandManager.commands()
                .stream()
                .filter(command -> command.getType() == CommandType.NORMAL_COMMAND)
                .forEach(command -> ActionManager.registerAction(
                        new CommandAction.Descriptor(command),
                        (mainFrame, properties) -> new CommandAction(mainFrame, properties, command)));

        TerminalActions.actionDescriptors().forEach(
                descriptor -> ActionManager.registerAction(descriptor,
                        (mainFrame, properties) -> new TerminalActions.NullAction(mainFrame, properties, descriptor)));
    }

    /**
     * Registration method for MuActions.
     *
     * @param actionDescriptor - ActionDescriptor instance of the action.
     * @param actionFactory - ActionFactory instance of the action.
     */
    public static void registerAction(ActionDescriptor actionDescriptor, ActionFactory actionFactory) {
        actionFactories.put(ActionId.asGenericAction(actionDescriptor.getId()), actionFactory);
        ActionProperties.addActionDescriptor(actionDescriptor);
    }

    /**
     * Registration method for MuActions.
     *
     * @param actionDescriptor - ActionDescriptor instance of the action.
     * @param actionFactory - ActionFactory instance of the action.
     */
    public static void registerAction(CommandAction.Descriptor actionDescriptor, ActionFactory actionFactory) {
        actionFactories.put(ActionId.asCommandAction(actionDescriptor.getId()), actionFactory);
        ActionProperties.addActionDescriptor(actionDescriptor);
    }

    /**
     * Registration method for MuActions.
     *
     * @param actionDescriptor - ActionDescriptor instance of the action.
     * @param actionFactory - ActionFactory instance of the action.
     */
    public static void registerAction(TerminalActions.Descriptor actionDescriptor, ActionFactory actionFactory) {
        actionFactories.put(ActionId.asTerminalAction(actionDescriptor.getId()), actionFactory);
        ActionProperties.addActionDescriptor(actionDescriptor);
    }

    /**
     * Return all ids of the registered actions.
     *
     * @return List of all registered actions' ids.
     */
    public static List<ActionId> getActionIds() {
        return new ArrayList<>(actionFactories.keySet());
    }

    /**
     * Return the id of MuAction in a given path.
     *
     * @param actionClassPath - path to MuAction class.
     * @return String representing the id of the MuAction in the specified path. null is returned if the given path is invalid.
     */
    public static String extrapolateId(String actionClassPath) {
        if (actionClassPath == null) {
            return null;
        }

        Matcher matcher = pattern.matcher(actionClassPath);
        return matcher.matches() ? matcher.group(1) : actionClassPath;
    }

    /**
     * Checks whether an MuAction is registered.
     *
     * @param actionId - id of MuAction.
     * @return true if an MuAction which is represented by the given id is registered, otherwise return false.
     */
    public static boolean isActionExist(ActionId actionId) {
        return actionId != null && actionFactories.containsKey(actionId);
    }

    /**
     * Convenience method that returns an instance of the action corresponding to the given <code>Command</code>,
     * and associated with the specified <code>MainFrame</code>. This method gets the ID of the relevant action,
     * passes it to {@link #getActionInstance(ActionId, MainFrame)} and returns the {@link MuAction} instance.
     *
     * @param command the command that is invoked by the returned action
     * @param mainFrame the MainFrame instance the action belongs to
     * @return a MuAction instance matching the given action ID and MainFrame, <code>null</code> if the
     * @see {@link #getActionInstance(ActionId, MainFrame)}
     * action could not be found or could not be instantiated.
     */
    public static MuAction getActionInstance(Command command, MainFrame mainFrame) {
        return getActionInstance(ActionId.asCommandAction(new CommandAction.Descriptor(command).getId()), mainFrame);
    }

    /**
     * Convenience method that returns an instance of the action denoted by the given ID, and associated with the
     * specified <code>MainFrame</code>. This method creates an ActionParameters with no initial property, passes it to
     * {@link #getActionInstance(ActionParameters, MainFrame)} and returns the {@link MuAction} instance.
     *
     * @param actionId ID of the action to instantiate
     * @param mainFrame the MainFrame instance the action belongs to
     * @return a MuAction instance matching the given action ID and MainFrame, <code>null</code> if the
     * @see {@link #getActionInstance(ActionParameters, MainFrame)}
     * action could not be found or could not be instantiated.
     */
    public static MuAction getActionInstance(ActionId actionId, MainFrame mainFrame) {
        return getActionInstance(new ActionParameters(actionId), mainFrame);
    }

    /**
     * @see {@link #getActionInstance(ActionId, MainFrame)}
     */
    public static MuAction getActionInstance(ActionType actionId, MainFrame mainFrame) {
        return getActionInstance(ActionId.asGenericAction(actionId.getId()), mainFrame);
    }

    /**
     * Returns an instance of the MuAction class denoted by the given ActionParameters and for the
     * specified MainFrame. If an existing instance corresponding to the same ActionParameters and MainFrame is found,
     * it is simply returned.
     * If no matching instance could be found, a new instance is created, added to the internal action instances map
     * (for further use) and returned.
     * If the action denoted by the specified ActionParameters cannot be found or cannot be instantiated,
     * <code>null</code> is returned.
     *
     * @param actionParameters a descriptor of the action to instantiate with initial properties
     * @param mainFrame the MainFrame instance the action belongs to
     * @return a MuAction instance matching the given ActionParameters and MainFrame, <code>null</code> if the
     * MuAction action denoted by the ActionParameters could not be found or could not be instantiated.
     */
    public static MuAction getActionInstance(ActionParameters actionParameters, MainFrame mainFrame) {
        Map<ActionParameters, ActionAndIdPair> mainFrameActions = mainFrameActionsMap.get(mainFrame);
        if (mainFrameActions == null) {
            synchronized (mainFrameActionsMap) {
                mainFrameActions = mainFrameActionsMap.get(mainFrame);
                if (mainFrameActions == null) {
                    mainFrameActions = new ConcurrentHashMap<>();
                    mainFrameActionsMap.put(mainFrame, mainFrameActions);
                }
            }
        }

        // Looks for an existing MuAction instance used by the specified MainFrame
        // or create one if necessary
        var result = mainFrameActions.computeIfAbsent(
                actionParameters, params -> createActionAndIdPair(params, mainFrame));

        return result != null ? result.getAction() : null;
    }

    @Nullable
    private static ActionAndIdPair createActionAndIdPair(ActionParameters actionParameters,
                                                         MainFrame mainFrame) {
        var actionId = actionParameters.getActionId();

        // Looks for the action's factory
        var actionFactory = actionFactories.get(actionId);
        if (actionFactory == null) {
            LOGGER.error("couldn't initiate action: " + actionId + ", its factory wasn't found");
            return null;
        }

        var properties = actionParameters.getInitProperties();
        // If no properties ConcurrentHashMap is specified in the action descriptor
        if (properties == null) {
            properties = Collections.emptyMap();
        }
        // else clone the ConcurrentHashMap to ensure that it doesn't get modified by action instances.
        // Since cloning is an expensive operation, this is done only if the hashtable is not empty.
        else if (!properties.isEmpty()) {
            var buffer = new ConcurrentHashMap<String, Object>(properties);
            properties = buffer;
        }

        // Instantiate the MuAction class
        var action = actionFactory.createAction(mainFrame, properties);

        // If the action's label has not been set yet, use the action descriptor's
        if (action.getLabel() == null) {
            // Retrieve the standard label entry from the dictionary and use it as this action's label
            String label = ActionProperties.getActionLabel(actionId);

            // Append '...' to the label if this action invokes a dialog when performed
            if (action.getClass().isAnnotationPresent(InvokesDialog.class)) {
                label += "...";
            }

            action.setLabel(label);

            // Looks for a standard label entry in the dictionary and if it is defined, use it as this action's tooltip
            String tooltip = ActionProperties.getActionTooltip(actionId);
            if (tooltip != null) {
                action.setToolTipText(tooltip);
            }
        }

        // If the action's accelerators have not been set yet, use the ones from ActionKeymap
        if (actionId.getType() != ActionId.ActionType.TERMINAL && action.getAccelerator() == null) {
            // Retrieve the standard accelerator (if any) and use it as this action's accelerator
            KeyStroke accelerator = ActionKeymap.getAccelerator(actionId);
            if (accelerator!=null) {
                action.setAccelerator(accelerator);
            }

            // Retrieve the standard alternate accelerator (if any) and use it as this action's alternate accelerator
            accelerator = ActionKeymap.getAlternateAccelerator(actionId);
            if (accelerator != null) {
                action.setAlternateAccelerator(accelerator);
            }
        }

        // If the action's icon has not been set yet, use the action descriptor's
        if (action.getIcon() == null) {
            // Retrieve the standard icon image (if any) and use it as the action's icon
            ImageIcon icon = ActionProperties.getActionIcon(actionId);
            if (icon != null) {
                action.setIcon(icon);
            }
        }
        return new ActionAndIdPair(action, actionId);
    }

    /**
     * Returns a Vector of all MuAction instances matching the specified action id.
     *
     * @param actionId the action id to compare instances against
     * @return  a Vector of all MuAction instances matching the specified action id
     */
    public static List<MuAction> getActionInstances(ActionId actionId) {
        List<MuAction> actionInstances = new Vector<MuAction>();

        // Iterate on all MainFrame instances
        for (Map<ActionParameters, ActionAndIdPair> actionParametersActionAndIdPairHashtable : mainFrameActionsMap.values()) {
            // Iterate on all the MainFrame's actions and their ids pairs
            for (ActionAndIdPair actionAndIdPair : actionParametersActionAndIdPairHashtable.values()) {
                if (actionAndIdPair.getId().equals(actionId)) {
                    // Found an action matching the specified class
                    actionInstances.add(actionAndIdPair.getAction());
                    // Jump to the next MainFrame
                    break;
                }
            }
        }

        return actionInstances;
    }

    /**
     * Convenience method that retrieves an instance of the action denoted by the given ID and associated
     * with the given {@link MainFrame} and calls {@link MuAction#performAction()} on it.
     * Returns <code>true</code> if an instance of the action could be retrieved and performed, <code>false</code>
     * if the MuAction could not be found or could not be instantiated.
     *
     * @param actionId ID of the action to perform
     * @param mainFrame the MainFrame the action belongs to
     * @return true if the action instance could be retrieved and the action performed, false otherwise
     */
    public static boolean performAction(ActionId actionId, MainFrame mainFrame) {
        return performAction(new ActionParameters(actionId), mainFrame);
    }

    /**
     * @see #performAction(ActionId, MainFrame)
     */
    public static boolean performAction(ActionType actionType, MainFrame mainFrame) {
        return performAction(ActionId.asGenericAction(actionType.getId()), mainFrame);
    }

    /**
     * Convenience method that retrieves an instance of the MuAction denoted by the given {@link ActionParameters}
     * and associated with the given {@link com.mucommander.ui.main.MainFrame} and calls {@link MuAction#performAction()} on it.
     * Returns <code>true</code> if an instance of the action could be retrieved and performed, <code>false</code>
     * if the MuAction could not be found or could not be instantiated.
     *
     * @param actionParameters the ActionParameters of the action to perform
     * @param mainFrame the MainFrame the action belongs to
     * @return true if the action instance could be retrieved and the action performed, false otherwise
     */
    public static boolean performAction(ActionParameters actionParameters, MainFrame mainFrame) {
        MuAction action = getActionInstance(actionParameters, mainFrame);
        if (action == null) {
            return false;
        }
        action.performAction();
        return true;
    }

    /**
     *  Helper class to represent a pair of instance and id of MuAction.
     */
    private static class ActionAndIdPair {
        private MuAction action;
        private ActionId id;

        public ActionAndIdPair(MuAction action, ActionId id) {
            this.action = action;
            this.id = id;
        }

        public MuAction getAction() { return action; }

        public ActionId getId() { return id; }
    }
}
